#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <time.h>
#include <sys/select.h>
#include <errno.h>
#include <sys/socket.h>

enum Code { REGISTER_SUCCESS = 1, LOGIN_SUCCESS = 0, 
        SERVER_SOCKET_ERROR = -20, SERVER_CONNECT_ERROR, SEND_ERROR, RECEIVE_ERROR, PASSWORD_INCORRECT,
        REGISTER_ERROR };

struct sockaddr_in server_addr;


int get_code_msg(int code) {
    switch (code)
    {
    case REGISTER_SUCCESS:
        printf("successfully registered!");
        break;
    case LOGIN_SUCCESS:
        printf("successfully logged in!");
        break;
    case SERVER_SOCKET_ERROR:
        printf("server socket error");
        break;
    case SERVER_CONNECT_ERROR:
        printf("can't connect to server\n");
        break;
    case SEND_ERROR:
        printf("can't send message");
        break;
    case RECEIVE_ERROR:
        printf("can't receive message");
        break;
    case PASSWORD_INCORRECT:
        printf("incorrect password");
        break;
    case REGISTER_ERROR:
        printf("can't automatically register");
        break;
    default:
        break;

    }
    return code;
}

int register_user(int server, const char *user, const char *password);

int connect_to_server(char *ip, short port) {
    
    int server = 0;
    if ((server = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        return SERVER_SOCKET_ERROR;
    }

    server_addr.sin_addr.s_addr = inet_addr(ip);
    server_addr.sin_port = htons(port);
    server_addr.sin_family = PF_INET;

    if (connect(server, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        
        return SERVER_CONNECT_ERROR;
    }

    return server;
}

int authenticate(int server, const char *user, const char *password) {
    const int buf_size = 128;
    char buf[buf_size];

    if (send(server, user, strlen(user), 0) < 0) {
        return SEND_ERROR;
    }

    memset(buf, 0, buf_size);
    int num_bytes = recv(server, buf, buf_size, 0);
    if (num_bytes < 0) {
        return RECEIVE_ERROR;
    }

    if (strncmp(buf, "y", 1) == 0) {
        // already registered
        if (strncmp(buf + 1, password, num_bytes - 1) == 0) {
            return LOGIN_SUCCESS;
        }
        else {
            close(server);
            return PASSWORD_INCORRECT;
        }
    }
    else {
        return register_user(server, user, password);
    }
}

int register_user(int server, const char *user, const char *password) {
    const int buf_size = 128;
    char buf[buf_size];


    if (send(server, password, strlen(password), 0) < 0) {
        return SEND_ERROR;
    }

    memset(buf, 0, buf_size);
    int num_bytes = recv(server, buf, buf_size, 0);
    if (num_bytes < 0) {
        return RECEIVE_ERROR;
    }

    if (strncmp(buf, "y", 1) == 0) {
        return REGISTER_SUCCESS;
    }
    else {
        return REGISTER_ERROR;
    }
}

void *receive_msg_handler(void *args) {
    const int buf_size = 128;
    char buf[buf_size];

    int server = *(int *)args;
    int num_bytes = 0;

    while (server) {
        memset(buf, 0, buf_size);
        num_bytes = recv(server, buf, buf_size - 1, 0);
        if (num_bytes < 0) {
            printf("receive error");
            break;
        }
        else if (num_bytes == 0) {
            printf("server disconnected");
            break;
        }
        else {
           
            printf("%s", buf);
        }
        
    }
    return NULL;
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("usage:%s user_name:password@ip_addr:port\n", argv[0]);
        return 1;
    }
    char *credentials = argv[1];
    const int buf_size = 128;
    char user[128], password[128], ip_addr[128], port_s[64], buf[128];

    sscanf(credentials, "%[^:]:%[^@]@%[^:]:%[0-9]", user, password, ip_addr, port_s);

    
    int server = 0;
    int code = 0;
    short port = atoi(port_s);


    if ((server = connect_to_server(ip_addr, port)) < 0) {
        return get_code_msg(server);
    }

    if ((code = authenticate(server, user, password)) < 0) {
        return get_code_msg(code);
    }

    pthread_t receive_tid;
    pthread_create(&receive_tid, NULL, receive_msg_handler, (void*)&server);
   
    while (1) {
        
        fflush(stdout);
        
        fgets(buf, buf_size, stdin);
        int len = strlen(buf);
        if (strcmp(buf, "quit\n") == 0) {
            close(server);
            break;
        }
        
        if (send(server, buf, strlen(buf), 0) < 0) {
            get_code_msg(SEND_ERROR);
            close(server);
        }
        printf("\033[1A\033[K");
        printf("%s:", user);
        printf("%s", buf);
    }

    printf("quitting....");
    //fflush(stdout);
    pthread_cancel(receive_tid);
    pthread_join(receive_tid, NULL);
    printf("success!\n");

    return 0;
}
